﻿///////////////////////////////////////////////////////////////////////////////
// 文件名： D3D9Render.cc
// 作者：   671643387@qq.com
// 日期：   2015年11月1日 下午6:15:23
// 简介：   
///////////////////////////////////////////////////////////////////////////////

#include <cassert>

#include "graphics/graphics/component/Mesh.hpp"

#include "D3D9Buffer.hpp"
#include "D3D9Render.hpp"
using namespace dx9::render;

D3D9Render::D3D9Render()
    : d3d9Impl_(NULL)
    , d3d9DevImpl_(NULL)
    , renderWin_(NULL)
    , eventDelegate_()
    , scene_()
{

}

D3D9Render::~D3D9Render()
{

}

///////////////////////////////////////////////////////////////////////////////

bool D3D9Render::Initialize()
{
    d3d9Impl_ = ::Direct3DCreate9(D3D_SDK_VERSION);
    if (CreateRenderWindow_() == NULL)
        return false;
    renderWin_->Show();
    return true;
}

void D3D9Render::Shutdown()
{
    if (d3d9DevImpl_)
        d3d9DevImpl_->Release();
    if (d3d9Impl_)
        d3d9Impl_->Release();
}

void D3D9Render::SetEventDelegate(::graphics::EventDelegate* delegate)
{
    eventDelegate_ = delegate;
}

void D3D9Render::SetScene(::graphics::graphics::ScenePtr scene)
{
    scene_ = scene;
    scene_->SetRender(this);
}

bool D3D9Render::BeginFrame()
{
    assert(d3d9DevImpl_ != NULL);
    return SUCCEEDED(d3d9DevImpl_->BeginScene());
}

void D3D9Render::EndFrame()
{
    assert(d3d9DevImpl_ != NULL);
    d3d9DevImpl_->EndScene();
}

void D3D9Render::RenderFrame()
{
    if (scene_)
    {
        scene_->RenderFrame();
    }
    d3d9DevImpl_->Present(NULL, NULL, NULL, NULL);
}

void D3D9Render::ClearColor(::graphics::byte r, ::graphics::byte g, ::graphics::byte b, ::graphics::byte a)
{
    d3d9DevImpl_->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(r, g, b), a / 255.0f, 0);
}

void D3D9Render::UseCamera(::graphics::graphics::scenenode::Camera::stProperties& properties)
{
    D3DXVECTOR3 position(properties.position.x, properties.position.y, properties.position.z);
    D3DXVECTOR3 lookat(properties.lookat.x, properties.lookat.y, properties.lookat.z);
    D3DXVECTOR3 upvec(properties.upvec.x, properties.upvec.y, properties.upvec.z);
    D3DXMATRIXA16 matView;

    D3DXMatrixLookAtLH(&matView, &position, &lookat, &upvec);
    d3d9DevImpl_->SetTransform(D3DTS_VIEW, &matView);

    D3DXMATRIXA16 matProj;

    if (properties.projection == ::graphics::graphics::scenenode::Camera::Perspective)
    {
        D3DXMatrixPerspectiveFovLH( &matProj, properties.fovy, properties.aspect,
            properties.near_, properties.far_ );
        d3d9DevImpl_->SetTransform( D3DTS_PROJECTION, &matProj );
    }
    else
    {
        D3DXMatrixOrthoLH(&matProj, properties.width, properties.height, 
            properties.near_, properties.far_);
        d3d9DevImpl_->SetTransform( D3DTS_PROJECTION, &matProj );
    }
}

::graphics::graphics::component::BufferPtr D3D9Render::CreateBuffer(::graphics::graphics::component::MeshRenderer::stProperties& properties)
{
    DWORD d3dFVF = 0;
    if (properties.fvf & ::graphics::graphics::component::Mesh::FVF_XYZ)
        d3dFVF |= D3DFVF_XYZ;
    if (properties.fvf & ::graphics::graphics::component::Mesh::FVF_COLOR)
        d3dFVF |= D3DFVF_DIFFUSE;

    void* pvData = NULL;
    LPDIRECT3DVERTEXBUFFER9 d3dVB = NULL;
    LPDIRECT3DINDEXBUFFER9 d3dIB = NULL;

    do 
    {
        if (FAILED(d3d9DevImpl_->CreateVertexBuffer(
            properties.vpSize, 0, d3dFVF, D3DPOOL_DEFAULT,
            &d3dVB, NULL)))
        {
            break;
        }

        if (FAILED(d3dVB->Lock(0, 0, &pvData, 0)))
            break;

        memcpy(pvData, properties.vp, properties.vpSize);

        if (properties.fvf & ::graphics::graphics::component::Mesh::FVF_INDEX)
        {
            int fmt = D3DFMT_INDEX16;
            if (properties.indexType == ::graphics::graphics::component::Mesh::IT_32)
            {
                fmt = D3DFMT_INDEX32;
            }

            if (FAILED(d3d9DevImpl_->CreateIndexBuffer(
                properties.ipSize, 0, (D3DFORMAT)fmt, D3DPOOL_DEFAULT,
                &d3dIB, NULL)))
            {
                break;
            }

            if (FAILED(d3dIB->Lock(0, 0, &pvData, 0)))
                break;

            memcpy(pvData, properties.ip, properties.ipSize);
        }

        dx9::D3D9Buffer* buffer = new dx9::D3D9Buffer();
        if (buffer)
        {
            buffer->SetVB(d3dVB);
            buffer->SetIB(d3dIB);
            return ::graphics::graphics::component::BufferPtr(buffer);
        }
    } while (false);

    if (d3dIB)
        d3dIB->Release();

    if (d3dVB)
        d3dVB->Release();

    return ::graphics::graphics::component::BufferPtr();
}

///////////////////////////////////////////////////////////////////////////////

bool D3D9Render::CreateRenderWindow_()
{
    assert(eventDelegate_ != NULL);

    D3D9RenderWindowPtr renderWin(new D3D9RenderWindow());
    if (!renderWin)
        return false;

    renderWin->SetEventDelegate(eventDelegate_);

    if (!renderWin->Initialize())
        return false;

    if (!InitializeDX9_(renderWin->GetHWND()))
        return false;

    renderWin_ = renderWin;
    return true;
}

bool D3D9Render::InitializeDX9_(HWND hWnd)
{
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory( &d3dpp, sizeof( d3dpp ) );
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

    int vp;
    CheckCaps_(d3dpp, vp);

    if( FAILED( d3d9Impl_->CreateDevice( D3DADAPTER_DEFAULT, 
        D3DDEVTYPE_HAL, hWnd,
        vp,
        &d3dpp, &d3d9DevImpl_ ) ) )
    {
        return false;
    }

    return true;
}

void D3D9Render::CheckCaps_(D3DPRESENT_PARAMETERS& d3dpp, int& vp)
{
    D3DCAPS9 caps;
    if (SUCCEEDED(d3d9Impl_->GetDeviceCaps(D3DADAPTER_DEFAULT,
        D3DDEVTYPE_HAL, &caps)))
    {
        if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
            vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
        else
            vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
    }
}